#pragma once
#include <iostream>
#include <unistd.h>
#include "Util.hpp"
#include <vector>
#include <unordered_map>
#include <sstream>
#include <algorithm>
#include "Log.hpp"
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <fcntl.h>

#define OK 200
#define NOT_FOUND 404
#define BAD_REQUEST 400
#define SERVER_ERROR 500

#define HTTP_VESION "HTTP/1.0"
#define WEB_ROOT "wwwroot"
#define HOME_PAGE "index.html"
#define PAGE_404 "404.html"
#define LINE_END "\r\n"

static std::string Code2Desc(int code)
{
    std::string str;
    switch (code)
    {
    case 200:
        str = "OK";
        break;
    case 400:
        str = "Bad Request";
        break;
    case 404:
        str = "Not Found";
        break;
    case 500:
        str = "Server Error";
        break;
    default:
        break;
    }
    return str;
}

static std::string Suffix2Desc(std::string suffix)
{
    static std::unordered_map<std::string, std::string> suffix2desc = {
        {".html", "text/html"},
        {".css", "text/css"},
        {".js", "application/javascript"},
        {".jpg", "application/x-jpg"},
        {".xml", "application/xml"},
    };

    auto iter = suffix2desc.find(suffix);
    if (iter != suffix2desc.end())
        return iter->second;

    return "text/html";
}

class HttpRequest
{
public:
    std::string _request_line;
    std::vector<std::string> _request_head;
    std::string _blank;
    std::string _body;

    std::string method;
    std::string uri;
    std::string version;

    std::unordered_map<std::string, std::string> header_kv;
    int content_lenth;
    std::string path;
    std::string query_string;
    std::string _suffix;

    bool _cgi;
    size_t _size;

    HttpRequest() : content_lenth(0), _cgi(false), _size(0) {}
};

class HttpResponse
{
public:
    std::string _state_line;
    std::vector<std::string> _response_head;
    std::string _blank;
    std::string _response_body;

    int _status_code;
    int _fd;

    HttpResponse() : _blank(LINE_END), _status_code(OK), _fd(-1) {}
};

class EndPoint
{
private:
    int _sock;
    HttpRequest _http_requset;
    HttpResponse _http_response;
    bool _stop;

private:
    bool RecvHttpRequestLine()
    {
        auto &line = _http_requset._request_line;
        if (Util::ReadLine(_sock, line) > 0)
        {
            line.resize(line.size() - 1);
            LOG(INFO, _http_requset._request_line);
        }
        else
        {
            _stop = true;
        }
        return _stop;
    }

    bool RecvHttpRequestHeader()
    {
        std::string line;
        while (1)
        {
            line.clear();
            if (Util::ReadLine(_sock, line) <= 0)
            {
                _stop = true;
                break;
            }
            if (line == "\n")
            {
                _http_requset._blank = line;
                break;
            }
            line.resize(line.size() - 1);
            _http_requset._request_head.push_back(line);
            LOG(INFO, line);
        }
        return _stop;
    }

    bool IsNeedRecvHttpRequestBody()
    {
        auto &method = _http_requset.method;
        if (method == "POST")
        {
            auto &header_kv = _http_requset.header_kv;
            auto iter = header_kv.find("Content-Length");
            if (iter != header_kv.end())
            {
                _http_requset.content_lenth = atoi(iter->second.c_str());
                return true;
            }
        }
        return false;
    }

    bool RecvHttpRequestBody()
    {
        if (IsNeedRecvHttpRequestBody())
        {
            int content_lenth = _http_requset.content_lenth;
            auto &body = _http_requset._body;
            char ch = 0;
            while (content_lenth)
            {
                ssize_t res = recv(_sock, &ch, 1, 0);
                if (res > 0)
                {
                    body.push_back(ch);
                    content_lenth--;
                }
                else
                {
                    _stop = true;
                    break;
                }
            }
        }
        return _stop;
    }

    void ParseHttpRequestLine()
    {
        std::stringstream ss(_http_requset._request_line);
        ss >> _http_requset.method >> _http_requset.uri >> _http_requset.version;
        auto &method = _http_requset.method;

        std::transform(method.begin(), method.end(), method.begin(), ::toupper);
    }

    void ParseHttpRequestHeader()
    {
        std::string key;
        std::string val;
        for (auto ite : _http_requset._request_head)
        {
            if (Util::CutString(ite, key, val, ": "))
            {
                _http_requset.header_kv.insert({key, val});
            }
        }
    }

    int ProcessNoneCgi()
    {
        _http_response._fd = open(_http_requset.path.c_str(), O_RDONLY);
        if (_http_response._fd >= 0)
        {
            LOG(INFO, _http_requset.path + " open success");
            return OK;
        }
        return NOT_FOUND;
    }

    int ProcessCgi()
    {
        auto &bin = _http_requset.path;
        int len = _http_requset.content_lenth;
        int code = OK;

        std::string query_string_env;
        std::string method_env;
        std::string content_length_env;

        int input[2];
        int output[2];
        if (pipe(input) < 0)
        {
            LOG(ERROR, "pipe input error");
            code = SERVER_ERROR;
            return code;
        }
        if (pipe(output) < 0)
        {
            LOG(ERROR, "pipe output error");
            code = SERVER_ERROR;
            return code;
        }

        pid_t pid = fork();
        if (pid == 0)
        {
            close(input[0]);
            close(output[1]);

            method_env = "METHOD=";
            method_env += _http_requset.method;
            putenv((char *)method_env.c_str());

            if (_http_requset.method == "GET")
            {
                query_string_env = "QUERY_STRING=";
                query_string_env += _http_requset.query_string;
                putenv((char *)query_string_env.c_str());
            }
            else if (_http_requset.method == "POST")
            {
                content_length_env = "CONTENT_LENGTH:";
                content_length_env += std::to_string(len);
                putenv((char *)content_length_env.c_str());
            }
            else
            {
                // do nothing
            }

            dup2(output[0], 0);
            dup2(input[1], 1);

            execl(bin.c_str(), bin.c_str(), nullptr);
            exit(1);
        }
        else if (pid < 0)
        {
            LOG(ERROR, "creat process error");
            code = SERVER_ERROR;
            return code;
        }
        else
        {
            close(input[1]);
            close(output[0]);

            if (_http_requset.method == "POST")
            {
                const char *ch = _http_requset._body.c_str();
                int total = 0;
                int sz = 0;
                while (total < _http_requset.content_lenth && (sz = write(output[1], ch + total, _http_requset._body.size() - total)) > 0)
                {
                    total += sz;
                }
            }
            char ch = 0;
            while (read(input[0], &ch, 1))
            {
                _http_response._response_body.push_back(ch);
            }

            int status = 0;
            pid_t res = waitpid(pid, &status, 0);
            if (res == pid)
            {
                if (WIFEXITED(status))
                {
                    if (WEXITSTATUS(status) == 0)
                    {
                        code = OK;
                    }
                    else
                    {
                        code = BAD_REQUEST;
                        return code;
                    }
                }
                else
                {
                    code = SERVER_ERROR;
                    return code;
                }
            }
            close(input[0]);
            close(output[1]);
        }
        return code;
    }

    void BulidOKRespond()
    {
        std::string head_line;
        head_line = "Content-Type: ";
        head_line += Suffix2Desc(_http_requset._suffix);
        head_line += LINE_END;
        _http_response._response_head.push_back(head_line);

        head_line = "Content-Length: ";
        if (_http_requset._cgi)
        {
            head_line += std::to_string(_http_response._response_body.size());
        }
        else
        {
            head_line += std::to_string(_http_requset._size);
        }
        head_line += LINE_END;
        _http_response._response_head.push_back(head_line);
    }

    void Handler_Error(std::string page)
    {

        _http_requset._cgi = false;
        _http_response._fd = open(page.c_str(), O_RDONLY);
        if (_http_response._fd > 0)
        {
            struct stat st;
            stat(page.c_str(), &st);
            _http_requset._size = st.st_size;

            std::string head_line;
            head_line = "Content-Type: text/html";
            head_line += LINE_END;
            _http_response._response_head.push_back(head_line);

            head_line = "Content-Length: ";
            head_line += std::to_string(st.st_size);
            head_line += LINE_END;
            _http_response._response_head.push_back(head_line);
        }
    }

    void BuildHttpResponseHandle()
    {
        auto &status_line = _http_response._state_line;
        status_line = HTTP_VESION;
        status_line += " ";
        status_line += std::to_string(_http_response._status_code);
        status_line += " ";
        status_line += Code2Desc(_http_response._status_code);
        status_line += LINE_END;

        std::string path;
        path = WEB_ROOT;
        path += "/";

        switch (_http_response._status_code)
        {
        case 200:
            BulidOKRespond();
            break;
        case 404:
            path += PAGE_404;
            Handler_Error(path);
            break;
        case 400:
            path += PAGE_404;
            Handler_Error(path);
            break;
        case 500:
            path += PAGE_404;
            Handler_Error(path);
            break;
        default:
            break;
        }
    }

public:
    EndPoint(int sock) : _sock(sock), _stop(false) {}

    void RecvHttpRequest()
    {
        if ((!RecvHttpRequestLine()) && (!RecvHttpRequestHeader()))
        {
            ParseHttpRequestLine();
            ParseHttpRequestHeader();
            RecvHttpRequestBody();
        }
    }

    bool IsStop()
    {
        return _stop;
    }

    void BuildHttpResponse()
    {
        std::string _path;
        struct stat st;
        size_t found;
        auto &code = _http_response._status_code;

        if (_http_requset.method != "GET" && _http_requset.method != "POST")
        {
            LOG(WARNING, "method is not right");
            code = BAD_REQUEST;
            goto END;
        }

        if (_http_requset.method == "GET")
        {
            size_t pos = _http_requset.uri.find('?');
            if (pos != std::string::npos)
            {
                Util::CutString(_http_requset.uri, _http_requset.path, _http_requset.query_string, "?");
                _http_requset._cgi = true;
            }
            else
            {
                _http_requset.path = _http_requset.uri;
            }
        }
        else if (_http_requset.method == "POST")
        {
            _http_requset.path = _http_requset.uri;
            _http_requset._cgi = true;
        }
        else
        {
            // do nothing
        }

        _path = _http_requset.path;
        _http_requset.path = WEB_ROOT;
        _http_requset.path += _path;

        if (_http_requset.path[_http_requset.path.size() - 1] == '/')
        {
            _http_requset.path += HOME_PAGE;
        }

        if (stat(_http_requset.path.c_str(), &st) == 0)
        {
            if (S_ISDIR(st.st_mode))
            {
                _http_requset.path += "/";
                _http_requset.path += HOME_PAGE;
                stat(_http_requset.path.c_str(), &st);
            }

            // 特殊处理可执行程序
            if ((st.st_mode & S_IXUSR) || (st.st_mode & S_IXGRP), st.st_mode & S_IXOTH)
            {
                _http_requset._cgi = true;
            }
            _http_requset._size = st.st_size;
        }
        else
        {
            std::string str = _http_requset.path;
            str += "Not Found!";
            LOG(WARNING, str);
            code = NOT_FOUND;
            goto END;
        }

        found = _http_requset.path.rfind(".");
        if (found == std::string::npos)
        {
            _http_requset._suffix = ".html";
        }
        else
        {
            _http_requset._suffix = _http_requset.path.substr(found);
        }

        if (_http_requset._cgi)
        {
            code = ProcessCgi();
        }
        else
        {
            code = ProcessNoneCgi();
        }

    END:
        BuildHttpResponseHandle();
    }

    void SendHttpResponse()
    {
        send(_sock, _http_response._state_line.c_str(), _http_response._state_line.size(), 0);
        for (auto &iter : _http_response._response_head)
        {
            send(_sock, iter.c_str(), iter.size(), 0);
        }
        send(_sock, _http_response._blank.c_str(), _http_response._blank.size(), 0);
        if (_http_requset._cgi)
        {
            auto &body = _http_response._response_body;
            size_t size = 0;
            size_t total = 0;
            const char *start = body.c_str();
            while (total < body.size() && (size = send(_sock, start + total, body.size() - total, 0)) > 0)
            {
                total += size;
            }
        }
        else
        {
            sendfile(_sock, _http_response._fd, nullptr, _http_requset._size);
            close(_http_response._fd);
        }
    }

    ~EndPoint()
    {
        close(_sock);
    }
};

class CallBack
{
public:

    CallBack(){}

    void operator ()(int sock)
    {
        HandlerRequest(sock);
    }

    void HandlerRequest(int sock)
    {
        LOG(INFO, "Handler Request Being...");
        // std::cout<<"get a new link.... : "<<sock<<std::endl;
        EndPoint *ep = new EndPoint(sock);
        ep->RecvHttpRequest();
        if (!ep->IsStop())
        {
            LOG(INFO, "Recv No Error, Begin Build And Send");
            ep->BuildHttpResponse();
            ep->SendHttpResponse();
        }
        else
        {
            LOG(INFO, "Recv Error, Stop Build And Send");
        }
        delete ep;

        LOG(INFO, "Handler Request End");
        close(sock);
    }

     ~CallBack(){}

};